﻿using BrightIdeasSoftware;
using ComponentFactory.Krypton.Navigator;
using ComponentFactory.Krypton.Ribbon;
using ComponentFactory.Krypton.Toolkit;
using LLMEvaluation.Framework;
using LLMEvaluation.Utils;
using StableDiffusionTools.Controls.KryptonHelper;
using StableDiffusionTools.Enums;
using StableDiffusionTools.Forms;
using StableDiffusionTools.Framework;
using StableDiffusionTools.Imaging;
using StableDiffusionTools.Interop;
using StableDiffusionTools.Oobabooga;
using StableDiffusionTools.RTF;
using StableDiffusionTools.Themes;
using StableDiffusionTools.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Message = StableDiffusionTools.Oobabooga.Message;

namespace LLMEvaluation.Forms
{
    [Export(typeof(IForm))]
    [ExportMetadata("Name", "LLM Evaluation")]
    [ExportMetadata("Image", "tag_green32")]
    public partial class LLMEvaluationForm : KryptonForm, IForm
    {
        public bool Busy { get; protected set; }
        public object KryptonToolTip { get; }

        private SettingsManager<LLMEvaluationSettings> settingsMan = new SettingsManager<LLMEvaluationSettings>(nameof(LLMEvaluationSettings), createBackups: false);
        private LLMEvaluationSettings settings;
        private bool closing;
        private TypedObjectListView<RunEntry> tolvRuns;
        private TypedObjectListView<ModelEntry> tolvModels;
        private TypedObjectListView<Message> tolvPrompt;
        private ModelFilter filter;
        private List<string> filterList = new List<string>();
        private FilterType filterType = FilterType.Prompt;
        private List<string> markingList = new List<string>();
        private Image imgNotTracking = StableDiffusionTools.Properties.Resources.lock_open16;
        private Image imgIsTracking = StableDiffusionTools.Properties.Resources.lock_go16;
        private Image imgGenerate = StableDiffusionTools.Properties.Resources.tag_red32;
        private Image imgGenerate16 = StableDiffusionTools.Properties.Resources.tag_flipped_red16;
        private Image imgStop = StableDiffusionTools.Properties.Resources.stop32;
        private Image imgStop16 = StableDiffusionTools.Properties.Resources.stop16;
        private Image imgAccept = StableDiffusionTools.Properties.Resources.accept32;
        private Image imgExclamation = StableDiffusionTools.Properties.Resources.exclamation32;
        private Image imgUnknown = StableDiffusionTools.Properties.Resources.unknown32;
        private Image imgBulletGreen = StableDiffusionTools.Properties.Resources.bullet_green16;
        private Image imgBulletRed = StableDiffusionTools.Properties.Resources.bullet_red16;
        private Image imgBulletBlack = StableDiffusionTools.Properties.Resources.bullet_black16;
        private List<string> modelNames = new List<string>();
        private GenerationJob job = null;
        private DragManager _dm;
        private float zoomFactor = 1.0f;
        private bool updatingStatus = false;
        private bool apiConnected = false;
        private int splitterWidthDefault;
        private int splitterHeightDefault;

        public LLMEvaluationForm()
        {
            InitializeComponent();
            GlobalTheme.Init();
            buttonSpecTheme.KryptonContextMenu = new KryptonThemeContextMenu();

            _dm = new DragManager();
            _dm.StateCommon.Feedback = PaletteDragFeedback.Block;
            _dm.DragTargetProviders.Add(bottomKryptonNavigator);
            _dm.DragTargetProviders.Add(topKryptonNavigator);
            _dm.DragTargetProviders.Add(leftKryptonNavigator);

            bottomKryptonNavigator.DragPageNotify = _dm;
            topKryptonNavigator.DragPageNotify = _dm;
            leftKryptonNavigator.DragPageNotify = _dm;

            Icon = Properties.Resources.llmeval;

            kryptonRibbonGroupButtonGenerate.ImageLarge = imgGenerate;
            kryptonRibbonGroupButtonLoadRuns.ImageLarge = StableDiffusionTools.Properties.Resources.document_import32;
            kryptonRibbonGroupButtonSaveRuns.ImageLarge = StableDiffusionTools.Properties.Resources.document_export32;
            kryptonRibbonGroupButtonSaveGlobalParameters.ImageSmall = StableDiffusionTools.Properties.Resources.disk16;

            kryptonRibbonGroupButtonConnect.ImageLarge = StableDiffusionTools.Properties.Resources.unknown32;
            kryptonRibbonGroupButtonUnloadModel.ImageLarge = StableDiffusionTools.Properties.Resources.control_eject32;
            kryptonRibbonGroupButtonSaveAPI.ImageSmall = StableDiffusionTools.Properties.Resources.disk16;

            kryptonRibbonQATButtonConnect.Image = StableDiffusionTools.Properties.Resources.bullet_black16;
            kryptonRibbonQATButtonConnect.Click += (s, e) =>
            {
                UpdateStatus();
            };

            kryptonRibbonQATButtonGenerate.Image = StableDiffusionTools.Properties.Resources.tag_flipped_red16;
            kryptonRibbonQATButtonGenerate.Click += async (s, e) =>
            {
                if (job == null)
                {
                    await Generate((List<RunEntry>)tolvRuns.Objects);
                }
                else
                {
                    kryptonRibbonGroupButtonGenerate.Enabled = false;
                    kryptonRibbonQATButtonGenerate.Enabled = false;
                    job?.Cancel();
                }
            };

            addEntryToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.bullet_add16;
            importEntryToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.document_import16;
            exportEntryToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.document_export16;
            generateToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.tag_green16;
            overviewToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.report16;
            removeToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.bullet_delete16;

            addModelToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.bullet_add16;
            generateModelToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.tag_green16;
            removeModelToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.bullet_delete16;
            addModelResponseToPromptToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.pencil_add16;
            clearModelResponseToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.pencil_delete16;

            addPromptToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.bullet_add16;
            removePromptToolStripMenuItem.Image = StableDiffusionTools.Properties.Resources.bullet_delete16;

            tsddbStopGeneration.Image = BitmapResizer.ResizeBitmap(StableDiffusionTools.Properties.Resources.stop32, new BitmapResizeGDISettings(GDIResizePresent.Default, new Size(16, 16)));

            imageListSmallRuns.Images.Add(InferenceState.Scheduled.ToString(), StableDiffusionTools.Properties.Resources.tag_green16);
            imageListSmallRuns.Images.Add(InferenceState.Generating.ToString(), StableDiffusionTools.Properties.Resources.tag_red16);
            imageListSmallModels.Images.Add("marked", StableDiffusionTools.Properties.Resources.pencil16);
            imageListSmallModels.Images.Add("scheduled", StableDiffusionTools.Properties.Resources.tag_green16);
            imageListSmallModels.Images.Add(InferenceState.Scheduled.ToString(), StableDiffusionTools.Properties.Resources.tag_green16);
            imageListSmallModels.Images.Add(InferenceState.Generating.ToString(), StableDiffusionTools.Properties.Resources.tag_red16);

            settings = settingsMan.LoadSettings();
            filterType = settings.LastFilterType;
            kryptonRibbonGroupTextBoxServer.Text = WebUIAPI.WebUIServer = settings.WebUIServer;
            kryptonRibbonGroupNumericUpDownPort.Value = WebUIAPI.WebUIPort = settings.WebUIPort;
            kryptonRibbonGroupTextBoxOAuthToken.Text = WebUIAPI.OAuthToken = settings.WebUIOAuthToken;

            kryptonContextMenuItemOnlyNewlyAddedModels.Click += KryptonContextMenuItemOnlyNewlyAddedModels_Click;
            kryptonContextMenuItemOnlySpecificModels.Click += kryptonContextMenuItemOnlySpecificModels_Click;
            kryptonContextMenuItemSkipCurrentModel.Click += kryptonContextMenuItemSkipCurrentModel_Click;
            kryptonContextMenuItemSkipCurrentRun.Click += kryptonContextMenuItemSkipCurrentRun_Click;

            kryptonRibbonGroupComboBoxInferenceOrder.Items.AddRange(EnumUtil.GetNames<InferenceOrder>());
            kryptonRibbonGroupComboBoxInferenceOrder.SelectedItem = settings.InferenceOrder.ToString();
            kryptonRibbonGroupComboBoxInferenceOrder.SelectedIndexChanged += (s, e) =>
            {
                if(EnumUtil.TryParse((string)kryptonRibbonGroupComboBoxInferenceOrder.SelectedItem, out InferenceOrder order))
                    settings.InferenceOrder = order;
            };

            if (kryptonRibbonGroupNumericUpDownTemperature.Enabled = kryptonRibbonGroupCheckBoxTemperature.Checked = settings.Temperature.HasValue)
                kryptonRibbonGroupNumericUpDownTemperature.Value = (decimal)settings.Temperature.Value;
            if (kryptonRibbonGroupNumericUpDownFrequencyPenalty.Enabled = kryptonRibbonGroupCheckBoxFrequencyPenalty.Checked = settings.FrequencyPenalty.HasValue)
                kryptonRibbonGroupNumericUpDownFrequencyPenalty.Value = (decimal)settings.FrequencyPenalty.Value;
            if (kryptonRibbonGroupNumericUpDownPresencePenalty.Enabled = kryptonRibbonGroupCheckBoxPresencePenalty.Checked = settings.PresencePenalty.HasValue)
                kryptonRibbonGroupNumericUpDownPresencePenalty.Value = (decimal)settings.PresencePenalty.Value;
            if (kryptonRibbonGroupNumericUpDownTopP.Enabled = kryptonRibbonGroupCheckBoxTopP.Checked = settings.TopP.HasValue)
                kryptonRibbonGroupNumericUpDownTopP.Value = (decimal)settings.TopP.Value;
            if (kryptonRibbonGroupNumericUpDownTopK.Enabled = kryptonRibbonGroupCheckBoxTopK.Checked = settings.TopK.HasValue)
                kryptonRibbonGroupNumericUpDownTopK.Value = settings.TopK.Value;
            if (kryptonRibbonGroupNumericUpDownMaxTokens.Enabled = kryptonRibbonGroupCheckBoxMaxTokens.Checked = settings.MaxTokens.HasValue)
                kryptonRibbonGroupNumericUpDownMaxTokens.Value = settings.MaxTokens.Value;
            if (kryptonRibbonGroupNumericUpDownMaxTokensSecond.Enabled = kryptonRibbonGroupCheckBoxMaxTokensSecond.Checked = settings.MaxTokensSecond.HasValue)
                kryptonRibbonGroupNumericUpDownMaxTokensSecond.Value = settings.MaxTokensSecond.Value;

            kryptonRibbonGroupCheckBoxTemperature.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownTemperature.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };
            kryptonRibbonGroupCheckBoxFrequencyPenalty.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownFrequencyPenalty.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };
            kryptonRibbonGroupCheckBoxPresencePenalty.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownPresencePenalty.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };
            kryptonRibbonGroupCheckBoxTopP.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownTopP.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };
            kryptonRibbonGroupCheckBoxTopK.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownTopK.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };
            kryptonRibbonGroupCheckBoxMaxTokens.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownMaxTokens.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };
            kryptonRibbonGroupCheckBoxMaxTokensSecond.CheckedChanged += (s, e) =>
            {
                kryptonRibbonGroupNumericUpDownMaxTokensSecond.Enabled = ((KryptonRibbonGroupCheckBox)s).Checked;
            };

            buttonSpecAnyRunsFilter.Image = StableDiffusionTools.Properties.Resources.filter16;
            buttonSpecAnyRunsFilter.Click += (s, e) =>
            {
                using (var filterDialogEnum = new MultiLineInputDialogWithEnum<FilterType>("Filter list by keywords", "Filter", filterList, true, "By type", filterType))
                {
                    if (settings.LastFilterSize != Size.Empty)
                        filterDialogEnum.Size = settings.LastFilterSize;

                    if (filterDialogEnum.ShowDialog() == DialogResult.OK)
                    {
                        if (filterDialogEnum.Result.Count > 0)
                        {
                            SetFilter(filterDialogEnum.Result, filterDialogEnum.ResultEnum);
                            buttonSpecAnyRunsFilter.Checked = ButtonCheckState.Checked;
                        }
                        else
                        {
                            RemoveFilter(filterDialogEnum.ResultEnum);
                            buttonSpecAnyRunsFilter.Checked = ButtonCheckState.NotCheckButton;
                        }
                    }
                    else
                    {
                        if(filterList.Count == 0)
                        {
                            buttonSpecAnyRunsFilter.Checked = ButtonCheckState.NotCheckButton;
                        }
                        else
                        {
                            buttonSpecAnyRunsFilter.Checked = ButtonCheckState.Checked;
                        }
                    }

                    settings.LastFilterSize = filterDialogEnum.Size;
                }
            };

            buttonSpecAnyResponseMarking.Image = StableDiffusionTools.Properties.Resources.pencil16;
            buttonSpecAnyResponseMarking.Click += (s, e) =>
            {
                using (var markDialog = new MultiLineInputDialog("Mark responses by keywords", "Marking", markingList, true))
                {
                    if (settings.LastMarkingSize != Size.Empty)
                        markDialog.Size = settings.LastFilterSize;

                    if (markDialog.ShowDialog() == DialogResult.OK)
                    {
                        if (markDialog.Result.Count > 0)
                        {
                            SetMarking(markDialog.Result);
                            buttonSpecAnyResponseMarking.Checked = ButtonCheckState.Checked;
                        }
                        else
                        {
                            RemoveMarking();
                            buttonSpecAnyResponseMarking.Checked = ButtonCheckState.NotCheckButton;
                        }
                    }
                    else
                    {
                        if (markingList.Count == 0)
                        {
                            buttonSpecAnyResponseMarking.Checked = ButtonCheckState.NotCheckButton;
                        }
                        else
                        {
                            buttonSpecAnyResponseMarking.Checked = ButtonCheckState.Checked;
                        }
                    }

                    settings.LastMarkingSize = markDialog.Size;
                }
            };

            buttonSpecAnyModelTrack.Checked = settings.TrackActiveModel ? ButtonCheckState.Checked : ButtonCheckState.Unchecked;
            buttonSpecAnyModelTrack.Image = settings.TrackActiveModel ? imgIsTracking : imgNotTracking;
            buttonSpecAnyModelTrack.Click += (s, e) =>
            {
                if (buttonSpecAnyModelTrack.Checked == ButtonCheckState.Checked)
                {
                    buttonSpecAnyModelTrack.Image = imgIsTracking;
                    if (Busy)
                    {
                        bool selectRun = true;
                        if(GetSelectedRun(out RunEntry selected))
                        {
                            if(selected.Inference == InferenceState.Generating)
                            {
                                selectRun = false;
                                bool hasSelectedModel = GetSelectedModel(out ModelEntry selectedModel);
                                if (!hasSelectedModel || (hasSelectedModel && selectedModel.Inference != InferenceState.Generating))
                                {
                                    for (int i = 0; i < selected.ModelEntries.Count; i++)
                                    {
                                        if (selected.ModelEntries[i].Inference == InferenceState.Generating)
                                        {
                                            olvModels.SelectedObject = selected.ModelEntries[i];
                                            olvModels.EnsureModelVisible(selected.ModelEntries[i]);
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        if (selectRun)
                        {
                            var runEntries = tolvRuns.Objects;
                            for (int i = 0; i < runEntries.Count; i++)
                            {
                                if (runEntries[i].Inference == InferenceState.Generating)
                                {
                                    olvRuns.SelectedObject = runEntries[i];
                                    olvRuns.EnsureModelVisible(runEntries[i]);
                                    return;
                                }
                            }
                        }
                    }
                }
                else
                    buttonSpecAnyModelTrack.Image = imgNotTracking;
            };

            kryptonContextMenuItemPopulateFromKnown.Image = StableDiffusionTools.Properties.Resources.package_add16;
            kryptonContextMenuItemClearAllResponses.Image = StableDiffusionTools.Properties.Resources.pencil_delete16;

            void kryptonNavigator_ShowContextMenu(object sender, ShowContextMenuArgs e)
            {
                if (e.Item == kryptonPageModels)
                {
                    e.Cancel = false;
                    e.KryptonContextMenu = kryptonContextMenuModels;
                    kryptonContextMenuItemPopulateFromKnown.Enabled = olvRuns.SelectedIndices.Count > 0 && modelNames.Count > 0;
                    kryptonContextMenuItemClearAllResponses.Enabled = olvRuns.SelectedIndices.Count > 0 && !Busy;
                }
            }

            leftKryptonNavigator.ShowContextMenu += kryptonNavigator_ShowContextMenu;
            topKryptonNavigator.ShowContextMenu += kryptonNavigator_ShowContextMenu;
            bottomKryptonNavigator.ShowContextMenu += kryptonNavigator_ShowContextMenu;

            kryptonContextMenuItemPopulateFromKnown.Click += (s, e) =>
            {
                bool hasSingleSelectedRun = GetSelectedRun(out RunEntry tag);
                if (GetSelectedRuns(out List<RunEntry> tags))
                {
                    for (int j = 0; j < tags.Count; j++)
                    {
                        var run = tags[j];
                        var existingModels = run.ModelEntries;
                        for (int i = 0; i < modelNames.Count; i++)
                        {
                            if (existingModels.FirstOrDefault((m) => m.Name.Equals(modelNames[i], StringComparison.InvariantCultureIgnoreCase)) != null)
                            {
                                continue;
                            }
                            var model = run.AddModel(modelNames[i]);
                            if(hasSingleSelectedRun && tag == run)
                                olvModels.AddObject(model);
                        }
                    }
                }
            };

            kryptonContextMenuItemClearAllResponses.Click += (s, e) =>
            {
                if (GetSelectedRuns(out List<RunEntry> tags))
                {
                    for (int i = 0; i < tags.Count; i++)
                    {
                        var tag = tags[i];
                        for (int j = 0; j < tag.ModelEntries.Count; j++)
                        {
                            tag.ModelEntries[j].ClearResponseMessages();
                        }
                    }
                }
            };

            leftKryptonNavigator.ToolTipManager.ShowInterval = 500;
            topKryptonNavigator.ToolTipManager.ShowInterval = 500;
            bottomKryptonNavigator.ToolTipManager.ShowInterval = 500;

            var lastRuns = ImportExportUtil.Import("LastRuns.json");
            if(lastRuns.Count > 0)
            {
                olvRuns.AddObjects(lastRuns);
                UpdateRibbonEnabledState();
            }

            RunsOverviewForm.ActionColor = settings.ActionColor;
            RunsOverviewForm.QuotedColor = settings.QuotedColor;

            //=================================================================
            //tolvRuns
            //=================================================================
            tolvRuns = new TypedObjectListView<RunEntry>(olvRuns);

            olvRuns.CellEditStarting += (sender, e) =>
            {
                if (e.Value is int currentVal)
                {
                    KryptonNumericUpDown knud = new KryptonNumericUpDown()
                    {
                        Increment = 1
                    };
                    if (e.Column == olvColumnRuns)
                        knud.Minimum = 1;
                    else if (e.Column == olvColumnSeed)
                    {
                        knud.Minimum = -1;
                        knud.Maximum = int.MaxValue;
                    }

                    knud.Bounds = e.CellBounds;
                    knud.Value = currentVal;
                    e.Control = knud;
                }
            };

            tolvRuns.GetColumn("Prompt").AspectGetter += (o) => o?.Prompt;
            olvColumnPrompt.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            tolvRuns.GetColumn("Models").AspectGetter += (o) => o?.Models;
            olvColumnModels.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            var runs = tolvRuns.GetColumn("Runs");
            runs.AspectGetter += (o) => o?.Runs;
            runs.AspectPutter += (o, a) =>
            {
                if (a is decimal newVal)
                {
                    o.Runs = (int)newVal;
                }
            };
            olvColumnRuns.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            var seed = tolvRuns.GetColumn("Seed");
            seed.AspectGetter += (o) => o?.Seed;
            seed.AspectPutter += (o, a) =>
            {
                if (a is decimal newVal)
                {
                    o.Seed = (int)newVal;
                }
            };
            olvColumnSeed.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            olvColumnRunScheduled.AspectToStringConverter = (e) => string.Empty;
            olvColumnRunScheduled.GroupKeyToTitleConverter = (e) => e as string;
            TypedColumn<RunEntry> tRunScheduled = new TypedColumn<RunEntry>(olvColumnRunScheduled)
            {
                AspectGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return e.Inference.ToString();
                },
                ImageGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return e.Inference.ToString();
                },
                GroupKeyGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return (e.Inference == InferenceState.Idle ? "Is Idle" : (e.Inference == InferenceState.Scheduled ? "Is Scheduled" : "Is Generating"));
                },
            };
            olvColumnRunScheduled.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            filter = new ModelFilter(delegate (object x)
            {
                RunEntry tag = (RunEntry)x;
                switch (filterType)
                {
                    case FilterType.Model:
                        for (int i = 0; i < filterList.Count; i++)
                        {
                            if (tag.Models.IndexOf(filterList[i], StringComparison.OrdinalIgnoreCase) != -1)
                                return true;
                        }
                        return false;
                    case FilterType.Prompt:
                    default:
                        for (int i = 0; i < filterList.Count; i++)
                        {
                            if (tag.Prompt.IndexOf(filterList[i], StringComparison.OrdinalIgnoreCase) != -1)
                                return true;
                        }
                        return false;
                }
            });

            //=================================================================
            //tolvModels
            //=================================================================
            tolvModels = new TypedObjectListView<ModelEntry>(olvModels);

            olvModels.CellEditStarting += (sender, e) =>
            {
                if (e.Value is string currentText)
                {
                    KryptonComboBox kcb = new KryptonComboBox
                    {
                        CausesValidation = false,
                        DropDownStyle = ComboBoxStyle.DropDownList
                    };
                    if (modelNames.Count > 0)
                    {
                        var currModels = tolvModels.Objects;
                        if(currModels.Count > 0)
                        {
                            var availableItems = modelNames.Where(p =>
                            {
                                if (p.Equals(currentText, StringComparison.CurrentCultureIgnoreCase))
                                    return true;

                                return currModels.All(p2 => !p2.Name.Equals(p, StringComparison.CurrentCultureIgnoreCase));
                            }).ToArray();

                            if (availableItems.Length > 0)
                                kcb.Items.AddRange(availableItems);
                            else if(!string.IsNullOrEmpty(currentText))
                                kcb.Items.Add(currentText);
                        }
                        else
                        {
                            kcb.Items.AddRange(modelNames.ToArray());
                        }
                    }
                    else if (!string.IsNullOrEmpty(currentText))
                    {
                        kcb.Items.Add(currentText);
                    }

                    kcb.SelectedItem = currentText;
                    kcb.Width = e.CellBounds.Width;
                    kcb.Location = new Point(e.CellBounds.X, e.CellBounds.Y + ((e.CellBounds.Height - kcb.Height) / 2));
                    e.Control = kcb;
                }
            };

            var models = tolvModels.GetColumn("Model");
            models.AspectGetter += (o) => o?.Name;
            models.AspectPutter += (o, a) =>
            {
                if (a is string newVal)
                {
                    if (string.IsNullOrEmpty(newVal))
                        newVal = "None";

                    o.Name = newVal;
                    if (GetSelectedRun(out RunEntry tag))
                    {
                        tag.UpdateModels();
                    }
                }
            };
            olvColumnModel.Renderer = new BaseRenderer() { UseGdiTextRendering = false, CanWrap = false };

            tolvModels.GetColumn("Response").AspectGetter += (o) => o?.Response;
            olvColumnResponse.Renderer = new BaseRenderer() { UseGdiTextRendering = false, CanWrap = false };

            olvColumnMark.AspectToStringConverter = (e) => string.Empty;
            olvColumnMark.GroupKeyToTitleConverter = (e) => e as string;
            TypedColumn<ModelEntry> tMarked = new TypedColumn<ModelEntry>(olvColumnMark)
            {
                AspectGetter = (e) =>
                {
                    return e?.Marks.Count > 0 ? "marked" : null;
                },
                ImageGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return (e.Marks.Count > 0 ? "marked" : null);
                },
                GroupKeyGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return (e.Marks.Count > 0 ? "Is Marked" : "Not Marked");
                },
            };
            olvColumnMark.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            olvColumnScheduled.AspectToStringConverter = (e) => string.Empty;
            olvColumnScheduled.GroupKeyToTitleConverter = (e) => e as string;
            TypedColumn<ModelEntry> tScheduled = new TypedColumn<ModelEntry>(olvColumnScheduled)
            {
                AspectGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return e.Inference.ToString();
                },
                ImageGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    return e.Inference.ToString();
                },
                GroupKeyGetter = (e) =>
                {
                    if (e == null)
                        return null;
                    switch (e.Inference)
                    {
                        case InferenceState.Scheduled:
                            return "Is Scheduled";
                        case InferenceState.Generating:
                            return "Is Generating";
                        case InferenceState.Idle:
                        default:
                            return "Is Idle";
                    }
                },
            };
            olvColumnScheduled.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            //=================================================================
            //tolvPrompt
            //=================================================================
            tolvPrompt = new TypedObjectListView<Message>(olvPrompt);
            olvPrompt.CellEditStarting += (sender, e) =>
            {
                if (e.Value is Role currentRole)
                {
                    KryptonComboBox kcb = new KryptonComboBox
                    {
                        CausesValidation = false,
                        DropDownStyle = ComboBoxStyle.DropDownList
                    };
                    kcb.Items.Add(Role.System);
                    kcb.Items.Add(Role.Assistant);
                    kcb.Items.Add(Role.User);
                    kcb.SelectedItem = currentRole;
                    kcb.Width = e.CellBounds.Width;
                    kcb.Location = new Point(e.CellBounds.X, e.CellBounds.Y + ((e.CellBounds.Height - kcb.Height) / 2));
                    e.Control = kcb;
                }
                else if (e.Value is string currentText)
                {
                    KryptonTextBox ktb = new KryptonTextBox
                    {
                        CausesValidation = false,
                        Multiline = true,
                        ScrollBars = ScrollBars.Vertical,
                        Bounds = e.CellBounds,
                        Text = currentText
                    };
                    e.Control = ktb;
                }
            };

            var role = tolvPrompt.GetColumn("Role");
            role.AspectGetter += (o) => o?.Role;
            role.AspectPutter += (o, a) =>
            {
                if (a is string newVal)
                {
                    o.Role = EnumUtil.Parse<Role>(newVal);
                    if (GetSelectedRun(out RunEntry tag))
                    {
                        tag.UpdatePromptMessages();
                    }
                }
            };
            olvColumnRole.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            var content = tolvPrompt.GetColumn("Content");
            content.AspectGetter += (o) => o?.Content;
            content.AspectPutter += (o, a) =>
            {
                if (a is string newVal)
                {
                    o.Content = newVal;
                    if (GetSelectedRun(out RunEntry tag))
                    {
                        tag.UpdatePromptMessages();
                    }
                }
            };

            olvColumnContent.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            FormClosing += (s, e) =>
            {
                if (e.CloseReason == CloseReason.UserClosing)
                {
                    if (Busy)
                    {
                        e.Cancel = true;
                    }
                    else
                    {
                        settings.LastFilterType = filterType;
                        settings.LastSidePanelWidth = kryptonSplitContainer1.ClientSize.Width - kryptonSplitContainer1.Panel1.ClientSize.Width;
                        settings.LastSidePanelHeight = kryptonSplitContainer2.SplitterDistance;
                        settings.TrackActiveModel = buttonSpecAnyModelTrack.Checked == ButtonCheckState.Checked;
                        settings.ShowRibbon = !kryptonRibbon1.MinimizedMode;
                        settings.QATLocation = kryptonRibbon1.QATLocation;
                        settings.ActionColor = RunsOverviewForm.ActionColor;
                        settings.QuotedColor = RunsOverviewForm.QuotedColor;

                        settings.QATButtons.Clear();
                        for (int j = 0; j < kryptonRibbon1.QATButtons.Count; j++)
                            if (kryptonRibbon1.QATButtons[j] is KryptonRibbonQATButton btn && btn.Visible)
                                settings.QATButtons.Add(btn.Text);

                        settings.LeftPages.Clear();
                        for (int i = 0; i < leftKryptonNavigator.Pages.Count; i++)
                            settings.LeftPages.Add(leftKryptonNavigator.Pages[i].Text);

                        settings.TopPages.Clear();
                        for (int i = 0; i < topKryptonNavigator.Pages.Count; i++)
                            settings.TopPages.Add(topKryptonNavigator.Pages[i].Text);

                        settings.BottomPages.Clear();
                        for (int i = 0; i < bottomKryptonNavigator.Pages.Count; i++)
                            settings.BottomPages.Add(bottomKryptonNavigator.Pages[i].Text);

                        settingsMan.SaveSettings(settings);

                        ImportExportUtil.Export("LastRuns.json", (List<RunEntry>)tolvRuns.Objects);

                        closing = true;
                    }
                }
            };

            for (int i = 0; i < settings.LeftPages.Count; i++)
                MovePage(GetPageByName(settings.LeftPages[i]), leftKryptonNavigator);

            for (int i = 0; i < settings.TopPages.Count; i++)
                MovePage(GetPageByName(settings.TopPages[i]), topKryptonNavigator);

            for (int i = 0; i < settings.BottomPages.Count; i++)
                MovePage(GetPageByName(settings.BottomPages[i]), bottomKryptonNavigator);

            kryptonRibbon1.MinimizedMode = !settings.ShowRibbon;
            kryptonRibbon1.QATLocation = settings.QATLocation;

            splitterWidthDefault = kryptonSplitContainer1.SplitterDistance;
            splitterHeightDefault = kryptonSplitContainer2.SplitterDistance;

            if (settings.LastSidePanelWidth != -1)
                kryptonSplitContainer1.SplitterDistance = kryptonSplitContainer1.ClientSize.Width - settings.LastSidePanelWidth;
            if (settings.LastSidePanelHeight != -1)
                kryptonSplitContainer2.SplitterDistance = settings.LastSidePanelHeight;
        }

        private void LLMEvaluationForm_Shown(object sender, EventArgs e)
        {
            for (int i = 0; i < settings.QATButtons.Count; i++)
            {
                for (int j = 0; j < kryptonRibbon1.QATButtons.Count; j++)
                {
                    if (kryptonRibbon1.QATButtons[j] is KryptonRibbonQATButton btn && btn.Text.Equals(settings.QATButtons[i], StringComparison.InvariantCultureIgnoreCase))
                    {
                        btn.SetVisible(true);
                    }
                }
            }
            UpdateStatus();
        }

        //=================================================================
        //Ribbon - API
        //=================================================================

        private async void kryptonRibbonGroupButtonGenerate_Click(object sender, EventArgs e)
        {
            if(job == null)
            {
                var runs = (List<RunEntry>)tolvRuns.Objects;
                if (runs.Count > 0)
                    await Generate(runs);
            }
            else
            {
                kryptonRibbonGroupButtonGenerate.Enabled = false;
                job?.Cancel();
            }
        }

        private void kryptonRibbonGroupButtonGenerate_DropDown(object sender, ContextMenuArgs e)
        {
            if(job == null)
            {
                kryptonContextMenuHeadingGenerate.Text = "Generate";
                kryptonContextMenuItemOnlyNewlyAddedModels.Visible = true;
                kryptonContextMenuItemOnlySpecificModels.Visible = true;
                kryptonContextMenuItemSkipCurrentModel.Visible = false;
                kryptonContextMenuItemSkipCurrentRun.Visible = false;
            }
            else
            {
                kryptonContextMenuHeadingGenerate.Text = "Skip";
                kryptonContextMenuItemOnlyNewlyAddedModels.Visible = false;
                kryptonContextMenuItemOnlySpecificModels.Visible = false;
                kryptonContextMenuItemSkipCurrentModel.Visible = true;
                kryptonContextMenuItemSkipCurrentRun.Visible = true;
            }
        }

        private async void KryptonContextMenuItemOnlyNewlyAddedModels_Click(object sender, EventArgs e)
        {
            if (job != null)
                return;

            var runs = (List<RunEntry>)tolvRuns.Objects;
            var genRuns = new List<GenerationRun>();

            for (int i = 0; i < runs.Count; i++)
            {
                List<ModelEntry> models = new List<ModelEntry>();
                RunEntry run = runs[i];
                for (int j = 0; j < run.ModelEntries.Count; j++)
                {
                    if (run.ModelEntries[j].ResponseMessages.Count == 0)
                    {
                        models.Add(run.ModelEntries[j]);
                    }
                }
                genRuns.Add(new GenerationRun(run, models));
            }

            await Generate(genRuns);
        }

        private async void kryptonContextMenuItemOnlySpecificModels_Click(object sender, EventArgs e)
        {
            if (job != null)
                return;

            var runs = (List<RunEntry>)tolvRuns.Objects;
            var uniqueModels = GenerationRun.GetUniqueModelNames(runs);
            var genRuns = new List<GenerationRun>();
            DialogResult result;
            using (var multiSelect = new MultiEntrySelectionDialog("Select Models for Inference", "Model Selection", uniqueModels))
            {
                if (settings.LastModelSelectionSize != Size.Empty)
                    multiSelect.Size = settings.LastModelSelectionSize;

                result = multiSelect.ShowDialog(this);
                if (result == DialogResult.OK)
                {
                    for (int i = 0; i < runs.Count; i++)
                    {
                        List<ModelEntry> models = new List<ModelEntry>();
                        var run = runs[i];
                        for (int j = 0; j < run.ModelEntries.Count; j++)
                        {
                            if (multiSelect.Result.Contains(run.ModelEntries[j].Name))
                            {
                                models.Add(run.ModelEntries[j]);
                            }
                        }
                        genRuns.Add(new GenerationRun(run, models));
                    }
                }

                settings.LastModelSelectionSize = multiSelect.Size;
            }

            if(result == DialogResult.OK)
                await Generate(genRuns);
        }

        private void kryptonContextMenuItemSkipCurrentModel_Click(object sender, EventArgs e)
        {
            if (job != null)
                job.SkipModel = true;
        }

        private void kryptonContextMenuItemSkipCurrentRun_Click(object sender, EventArgs e)
        {
            if (job != null)
                job.SkipRun = true;
        }

        private void kryptonRibbonGroupButtonLoadRuns_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog
            {
                DefaultExt = "json",
                Filter = "JSON File|*.json|All Files|*.*",
                InitialDirectory = settings.LastPath
            };
            if (ofd.ShowDialog(this) == DialogResult.OK)
            {
                if (tolvRuns.Objects.Count > 0 && ConfirmationDialog.ShowDialogIf(this, ref settings.LoadConfirmation, "Loading runs will remove all existing runs, are you sure?") != DialogResult.Yes)
                    return;

                var imported = ImportExportUtil.Import(ofd.FileName);
                if (imported.Count > 0)
                {
                    ClearAll();
                    for (int i = 0; i < imported.Count; i++)
                    {
                        var item = imported[i];
                        var models = item.ModelEntries;
                        for (int j = 0; j < models.Count; j++)
                        {
                            models[j].SetMarked(markingList);
                        }

                        AddRun(item, imported.Count == 1);

                        if (i == 0)
                            settings.LastPath = Path.GetDirectoryName(ofd.FileNames[i]);
                    }
                }
                else
                {
                    KryptonMessageBox.Show(this, "Failed to import file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void kryptonRibbonGroupButtonSaveRuns_Click(object sender, EventArgs e)
        {
            if (tolvRuns.Objects is List<RunEntry> runs)
            {
                SaveFileDialog sfd = new SaveFileDialog
                {
                    DefaultExt = "json",
                    Filter = "JSON File|*.json|All Files|*.*",
                    InitialDirectory = settings.LastPath
                };
                if (sfd.ShowDialog(this) == DialogResult.OK)
                {
                    ImportExportUtil.Export(sfd.FileName, runs);
                    settings.LastPath = Path.GetDirectoryName(sfd.FileName);
                }
            }
        }

        private void kryptonRibbonGroupButtonSaveGlobalParameters_Click(object sender, EventArgs e)
        {
            settings.Temperature = kryptonRibbonGroupCheckBoxTemperature.Checked ? (float?)kryptonRibbonGroupNumericUpDownTemperature.Value : null;
            settings.FrequencyPenalty = kryptonRibbonGroupCheckBoxFrequencyPenalty.Checked ? (float?)kryptonRibbonGroupNumericUpDownFrequencyPenalty.Value : null;
            settings.PresencePenalty = kryptonRibbonGroupCheckBoxPresencePenalty.Checked ? (float?)kryptonRibbonGroupNumericUpDownPresencePenalty.Value : null;
            settings.TopP = kryptonRibbonGroupCheckBoxTopP.Checked ? (float?)kryptonRibbonGroupNumericUpDownTopP.Value : null;
            settings.TopK = kryptonRibbonGroupCheckBoxTopK.Checked ? (int?)kryptonRibbonGroupNumericUpDownTopK.Value : null;
            settings.MaxTokens = kryptonRibbonGroupCheckBoxMaxTokens.Checked ? (int?)kryptonRibbonGroupNumericUpDownMaxTokens.Value : null;
            settings.MaxTokensSecond = kryptonRibbonGroupCheckBoxMaxTokensSecond.Checked ? (int?)kryptonRibbonGroupNumericUpDownMaxTokensSecond.Value : null;
        }

        //=================================================================
        //Ribbon - Inference
        //=================================================================

        private void kryptonRibbonGroupButtonConnect_Click(object sender, EventArgs e)
        {
            UpdateStatus();
        }

        private async void kryptonRibbonGroupButtonUnloadModel_Click(object sender, EventArgs e)
        {
            if (Busy)
                return;

             var unload  = await WebUIAPI.UnloadModelInternal(1000);
            if (unload.Success)
            {
                var currentModel = await WebUIAPI.GetModelInfoInternal(1000);
                if (currentModel.Success)
                {
                    kryptonRibbonGroupTextBoxLoadedModel.Text = currentModel.Data.ModelName;
                }
            }
        }

        private void kryptonRibbonGroupButtonSaveAPI_Click(object sender, EventArgs e)
        {
            settings.WebUIServer = WebUIAPI.WebUIServer = kryptonRibbonGroupTextBoxServer.Text;
            settings.WebUIPort = WebUIAPI.WebUIPort = (int)kryptonRibbonGroupNumericUpDownPort.Value;
            settings.WebUIOAuthToken = WebUIAPI.OAuthToken = kryptonRibbonGroupTextBoxOAuthToken.Text;
        }

        //=================================================================
        //IndexChanged Handlers
        //=================================================================
        private void olvRuns_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (olvModels.IsCellEditing)
                olvModels.FinishCellEdit();
            if (olvPrompt.IsCellEditing)
                olvPrompt.FinishCellEdit();

            UpdateTabPagesFromRunSelection();
        }

        private void olvModels_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (GetSelectedModel(out ModelEntry model))
            {
                PopulateResponse(model.ResponseFormatted);
            }
            else
            {
                kryptonTBResponse.Clear();
            }
        }

        private void topKryptonNavigator_SelectedPageChanged(object sender, EventArgs e)
        {

        }

        private void bottomKryptonNavigator_SelectedPageChanged(object sender, EventArgs e)
        {

        }

        //=================================================================
        //KeyDown Handlers
        //=================================================================
        private void olvRuns_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                olvRuns.DeselectAll();
                e.SuppressKeyPress = true;
            }
            else if (e.KeyCode == Keys.Delete && !Busy)
            {
                if (RemoveSelectedRuns())
                {
                    e.SuppressKeyPress = true;
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.C)
            {
                if (GetSelectedRuns(out List<RunEntry> items))
                {
                    if (ImportExportUtil.TrySerialize(items, out string json))
                    {
                        ClipboardUtil.SetText(json);
                        e.SuppressKeyPress = true;
                        tsslStatus.Text = $"Copied, {items.Count} Run{(items.Count > 1 ? "s" : "")}";
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.V)
            {
                if (ClipboardUtil.TryGetText(out string text))
                {
                    if (ImportExportUtil.TryDeserialize(text, out List<RunEntry> items))
                    {
                        for (int i = 0; i < items.Count; i++)
                        {
                            items[i].UpdateAll();
                            for (int j = 0; j < items[i].ModelEntries.Count; j++)
                            {
                                items[i].ModelEntries[j].SetMarked(markingList);
                            }
                        }

                        if (GetSelectedRun(out RunEntry run))
                        {
                            var item = olvRuns.ModelToItem(run);
                            olvRuns.InsertObjects(item.Index, items);
                        }
                        else
                        {
                            olvRuns.AddObjects(items);
                        }

                        olvRuns.SelectedObjects = items;
                        UpdateRibbonEnabledState();
                        e.SuppressKeyPress = true;
                        tsslStatus.Text = $"Pasted, {items.Count} Run{(items.Count > 1 ? "s" : "")}";
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.X && !Busy)
            {
                if (GetSelectedRuns(out List<RunEntry> items))
                {
                    if (ImportExportUtil.TrySerialize(items, out string json))
                    {
                        ClipboardUtil.SetText(json);
                        olvRuns.RemoveObjects(items);
                        UpdateRibbonEnabledState();
                        e.SuppressKeyPress = true;
                        tsslStatus.Text = $"Cut, {items.Count} Run{(items.Count > 1 ? "s" : "")}";
                    }
                }
            }
        }

        private void olvPrompt_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                olvPrompt.DeselectAll();
                e.SuppressKeyPress = true;
            }
            else if (e.KeyCode == Keys.Delete)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (GetSelectedPrompts(out List<Message> items))
                    {
                        run.RemovePromptMessages(items);
                        olvPrompt.RemoveObjects(items);
                        e.SuppressKeyPress = true;
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.C)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (GetSelectedPrompts(out List<Message> items))
                    {
                        if (ImportExportUtil.TrySerialize(items, out string json))
                        {
                            ClipboardUtil.SetText(json);
                            e.SuppressKeyPress = true;
                            tsslStatus.Text = $"Copied, {items.Count} Prompt{(items.Count > 1 ? "s" : "")}";
                        }
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.V)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (ClipboardUtil.TryGetText(out string text))
                    {
                        if (ImportExportUtil.TryDeserialize(text, out List<Message> items))
                        {
                            if(GetSelectedPrompt(out Message prompt))
                            {
                                var item = olvPrompt.ModelToItem(prompt);
                                run.AddPromptMessages(items, item.Index);
                                olvPrompt.InsertObjects(item.Index, items);
                            }
                            else
                            {
                                run.AddPromptMessages(items);
                                olvPrompt.AddObjects(items);
                            }

                            olvPrompt.SelectedObjects = items;

                            e.SuppressKeyPress = true;
                            tsslStatus.Text = $"Pasted, {items.Count} Prompt{(items.Count > 1 ? "s" : "")}";
                        }
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.X)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (GetSelectedPrompts(out List<Message> items))
                    {
                        if (ImportExportUtil.TrySerialize(items, out string json))
                        {
                            ClipboardUtil.SetText(json);
                            run.RemovePromptMessages(items);
                            olvPrompt.RemoveObjects(items);
                            e.SuppressKeyPress = true;
                            tsslStatus.Text = $"Cut, {items.Count} Prompt{(items.Count > 1 ? "s" : "")}";
                        }
                    }
                }
            }
        }

        private void olvModels_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                olvModels.DeselectAll();
                e.SuppressKeyPress = true;
            }
            else if (e.KeyCode == Keys.Delete && !Busy)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (GetSelectedModels(out List<ModelEntry> items))
                    {
                        run.RemoveModels(items);
                        olvModels.RemoveObjects(items);
                        e.SuppressKeyPress = true;
                    }
                }
            }
            else if(e.Modifiers == Keys.Control && e.KeyCode == Keys.C)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (GetSelectedModels(out List<ModelEntry> items))
                    {
                        if(ImportExportUtil.TrySerialize(items, out string json))
                        {
                            ClipboardUtil.SetText(json);
                            e.SuppressKeyPress = true;
                            tsslStatus.Text = $"Copied, {items.Count} Model{(items.Count > 1 ? "s" : "")}";
                        }
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.V)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if(ClipboardUtil.TryGetText(out string text))
                    {
                        if (ImportExportUtil.TryDeserialize(text, out List<ModelEntry> items))
                        {

                            for (int i = 0; i < items.Count; i++)
                            {
                                items[i].Update();
                                items[i].SetMarked(markingList);
                            }

                            if (GetSelectedModel(out ModelEntry model))
                            {
                                var item = olvModels.ModelToItem(model);
                                run.AddModels(items, item.Index);
                                olvModels.InsertObjects(item.Index, items);
                            }
                            else
                            {
                                run.AddModels(items);
                                olvModels.AddObjects(items);
                            }

                            olvModels.SelectedObjects = items;

                            e.SuppressKeyPress = true;
                            tsslStatus.Text = $"Pasted, {items.Count} Model{(items.Count > 1 ? "s" : "")}";
                        }
                    }
                }
            }
            else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.X && !Busy)
            {
                if (GetSelectedRun(out RunEntry run))
                {
                    if (GetSelectedModels(out List<ModelEntry> items))
                    {
                        if (ImportExportUtil.TrySerialize(items, out string json))
                        {
                            ClipboardUtil.SetText(json);
                            run.RemoveModels(items);
                            olvModels.RemoveObjects(items);
                            e.SuppressKeyPress = true;
                            tsslStatus.Text = $"Cut, {items.Count} Model{(items.Count > 1 ? "s" : "")}";
                        }
                    }
                }
            }
        }

        //=================================================================
        //ContextMenu Handlers
        //=================================================================
        private void ctxRuns_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            var selectionCount = olvRuns.SelectedIndices;
            bool hasRunsSelected = selectionCount.Count > 0;
            bool hasRunSelected = selectionCount.Count == 1;
            removeToolStripMenuItem.Visible = hasRunsSelected;
            removeToolStripMenuItem.Enabled = !Busy;
            generateToolStripMenuItem.Visible = hasRunsSelected;
            generateToolStripMenuItem.Enabled = !Busy;
            overviewToolStripMenuItem.Visible = hasRunsSelected;
            overviewToolStripMenuItem.Enabled = !Busy;
            exportEntryToolStripMenuItem.Visible = hasRunsSelected;
            exportEntryToolStripMenuItem.Enabled = !Busy;
        }

        private void addEntryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AddRun(new RunEntry(), true);
        }

        private void importEntryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog
            {
                DefaultExt = "json",
                Filter = "JSON File|*.json|All Files|*.*",
                Multiselect = true,
                InitialDirectory = settings.LastPath
            };
            if (ofd.ShowDialog(this) == DialogResult.OK)
            {
                var imported = ImportExportUtil.Import(ofd.FileNames);
                if (imported.Count > 0)
                {
                    for (int i = 0; i < imported.Count; i++)
                    {
                        var item = imported[i];
                        var models = item.ModelEntries;
                        for (int j = 0; j < models.Count; j++)
                        {
                            models[j].SetMarked(markingList);
                        }

                        AddRun(item, imported.Count == 1);

                        if (i == 0)
                            settings.LastPath = Path.GetDirectoryName(ofd.FileNames[i]);
                    }
                }
                else
                {
                    KryptonMessageBox.Show(this, "Failed to import file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void exportEntryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRuns(out List<RunEntry> tags))
            {
                SaveFileDialog sfd = new SaveFileDialog
                {
                    DefaultExt = "json",
                    Filter = "JSON File|*.json|All Files|*.*",
                    InitialDirectory = settings.LastPath
                };
                if (sfd.ShowDialog(this) == DialogResult.OK)
                {
                    if (tags.Count == 1)
                        ImportExportUtil.Export(sfd.FileName, tags[0]);
                    else
                        ImportExportUtil.Export(sfd.FileName, tags);

                    settings.LastPath = Path.GetDirectoryName(sfd.FileName);
                }
            }
        }

        private async void generateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRuns(out List<RunEntry> runs))
            {
                await Generate(runs);
            }
        }

        private void overviewToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRuns(out List<RunEntry> items))
            {
                using (RunsOverviewForm runsOverviewForm = new RunsOverviewForm(items))
                {
                    if (settings.LastOverviewSize != Size.Empty)
                        runsOverviewForm.Size = settings.LastOverviewSize;

                    runsOverviewForm.ShowDialog(this);

                    settings.LastOverviewSize = runsOverviewForm.Size;
                }
            }
        }

        private void removeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            RemoveSelectedRuns();
        }

        private void ctxModels_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            var selectedCount = olvRuns.SelectedIndices;
            if (selectedCount.Count == 1)
            {
                var models = tolvModels.SelectedObjects;
                bool hasModelsSelected = models.Count > 0;
                bool hasModelSelected = models.Count == 1;
                removeModelToolStripMenuItem.Visible = hasModelsSelected;
                removeModelToolStripMenuItem.Enabled = !Busy;
                generateModelToolStripMenuItem.Visible = hasModelsSelected;
                generateModelToolStripMenuItem.Enabled = !Busy;
                addModelResponseToPromptToolStripMenuItem.Visible = hasModelSelected && models[0].HasSingleResponseMessage;
                clearModelResponseToolStripMenuItem.Visible = hasModelsSelected && !Busy;
            }
            else
            {
                e.Cancel = true;
            }
        }

        private void AddModelToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry tag))
            {
                var model = tag.AddModel("None");
                olvModels.AddObject(model);

                var selectionCount = olvModels.SelectedIndices;
                if (selectionCount.Count == 0)
                {
                    olvModels.SelectedObject = model;
                    olvModels.EnsureModelVisible(model);
                }
            }
        }

        private async void generateModelToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry run) && GetSelectedModels(out List<ModelEntry> models))
            {
                await Generate(run, models);
            }
        }

        private void removeModelToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry tag))
            {
                if (GetSelectedModels(out List<ModelEntry> items))
                {
                    tag.RemoveModels(items);
                    olvModels.RemoveObjects(items);
                }
            }
        }

        private void addModelResponseToPromptToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry tag) && GetSelectedModel(out ModelEntry model))
            {
                var message = tag.AddPromptMessage(model.ResponseMessages[0].Role, model.ResponseMessages[0].Content);
                olvPrompt.UpdateObject(message);
                ((KryptonNavigator)kryptonPagePrompt.KryptonParentContainer).SelectedPage = kryptonPagePrompt;
                olvPrompt.SelectedObject = message;
                olvPrompt.EnsureModelVisible(message);
            }
        }

        private void clearModelResponseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry tag) && GetSelectedModels(out List<ModelEntry> models))
            {
                for (int i = 0; i < models.Count; i++)
                {
                    models[i].ClearResponseMessages();
                }
            }
        }

        private void ctxPrompt_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            var selectedCount = olvRuns.SelectedIndices;
            if (selectedCount.Count == 1)
            {
                var selectionCount = olvPrompt.SelectedIndices;
                bool hasPromptsSelected = selectionCount.Count > 0;
                removePromptToolStripMenuItem.Visible = hasPromptsSelected;
            }
            else
            {
                e.Cancel = true;
            }
        }

        private void addPromptToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry tag))
            {
                var items = tolvPrompt.Objects;
                var lastItem = items.Count == 0 ? null : items[items.Count - 1];
                Role role = lastItem == null ? Role.System : (lastItem.Role == Role.System || lastItem.Role == Role.Assistant ? Role.User : Role.Assistant);
                var model = tag.AddPromptMessage(role, "");
                olvPrompt.AddObject(model);
                var selectionCount = olvPrompt.SelectedIndices;
                if (selectionCount.Count == 0)
                {
                    olvPrompt.SelectedObject = model;
                    olvPrompt.EnsureModelVisible(model);
                }
            }
        }

        private void removePromptToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (GetSelectedRun(out RunEntry tag))
            {
                if (GetSelectedPrompts(out List<Message> items))
                {
                    tag.RemovePromptMessages(items);
                    olvPrompt.RemoveObjects(items);
                }
            }
        }

        //=================================================================
        //Response Handlers
        //=================================================================
        private void kryptonTBResponse_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Control && e.KeyCode == Keys.Add)
            {
                zoomFactor = MathUtil.Clamp(zoomFactor + 0.1f, 0.1f, 10.0f);
                kryptonTBResponse.ZoomFactor = zoomFactor;
            }
            else if (e.Control && e.KeyCode == Keys.Subtract)
            {
                zoomFactor = MathUtil.Clamp(zoomFactor - 0.1f, 0.1f, 10.0f);
                kryptonTBResponse.ZoomFactor = zoomFactor;
            }
        }

        //=================================================================
        //StatusStrip Handlers
        //=================================================================
        private async void tsddbStopGeneration_Click(object sender, EventArgs e)
        {
            tsddbStopGeneration.Enabled = false;
            job?.Cancel();
            await WebUIAPI.StopGenerationInternal();
        }

        //=================================================================
        //Private Methods
        //=================================================================
        private void SetFilter(List<string> filterOn, FilterType type)
        {
            filterList = filterOn;
            filterType = type;
            olvRuns.ModelFilter = filter;
        }

        private void RemoveFilter() => RemoveFilter(filterType);
        private void RemoveFilter(FilterType type)
        {
            filterType = type;
            filterList = new List<string>();
            olvRuns.ModelFilter = null;
        }

        private void SetMarking(List<string> markOn)
        {
            markingList = markOn;
            if (GetSelectedModel(out ModelEntry item) && item.ResponseMessages.Count > 0)
            {
                PopulateResponse(item.ResponseFormatted);
            }

            var runs = tolvRuns.Objects;
            for (int j = 0; j < runs.Count; j++)
            {
                var models = runs[j].ModelEntries;
                for (int i = 0; i < models.Count; i++)
                {
                    models[i].SetMarked(markingList);
                }
            }
        }

        private void RemoveMarking()
        {
            markingList = new List<string>();
            if (GetSelectedModel(out ModelEntry item) && item.ResponseMessages.Count > 0)
            {
                PopulateResponse(item.ResponseFormatted);
            }

            var runs = tolvRuns.Objects;
            for (int j = 0; j < runs.Count; j++)
            {
                var models = runs[j].ModelEntries;
                for (int i = 0; i < models.Count; i++)
                {
                    models[i].SetMarked(markingList);
                }
            }
        }

        private void PopulateResponse(string responseFormatted)
        {
            kryptonTBResponse.SuspendPaint();
            int selStart = kryptonTBResponse.SelectionStart;
            int selLen = kryptonTBResponse.SelectionLength;

            kryptonTBResponse.Text = responseFormatted;
            if (markingList.Count > 0)
            {
                var matches = Regex.Matches(kryptonTBResponse.Text, string.Join("|", markingList.Select((m) => Regex.Escape(m))), RegexOptions.IgnoreCase, new TimeSpan(0, 0, 30));
                if (matches.Count > 0)
                {
                    for (int i = 0; i < matches.Count; i++)
                    {
                        var match = matches[i];
                        kryptonTBResponse.SelectionStart = match.Index;
                        kryptonTBResponse.SelectionLength = match.Length;
                        kryptonTBResponse.SelectionBackColor = Color.PaleGoldenrod;
                    }
                }
            }

            User32.VerticalScrollPageBottom(kryptonTBResponse.RichTextBox.Handle);
            kryptonTBResponse.SelectionStart = kryptonTBResponse.Text.Length;

            kryptonTBResponse.ResumePaint();
        }

        private void ClearAll()
        {
            olvRuns.ClearObjects();
            olvModels.ClearObjects();
            olvPrompt.ClearObjects();
            kryptonTBResponse.Clear();
        }

        private void UpdateTabPagesFromRunSelection()
        {
            if (GetSelectedRun(out RunEntry tag))
            {
                olvModels.SuspendLayout();
                olvModels.BeginUpdate();
                olvModels.ClearObjects();
                olvModels.SetObjects(tag.ModelEntries);
                olvModels.ResumeLayout();
                olvModels.EndUpdate();

                olvPrompt.SuspendLayout();
                olvPrompt.BeginUpdate();
                olvPrompt.ClearObjects();
                olvPrompt.SetObjects(tag.PromptMessages);
                olvPrompt.ResumeLayout();
                olvPrompt.EndUpdate();

                if (Busy && buttonSpecAnyModelTrack.Checked == ButtonCheckState.Checked)
                {
                    for (int i = 0; i < tag.ModelEntries.Count; i++)
                    {
                        if(tag.ModelEntries[i].Inference == InferenceState.Generating)
                        {
                            olvModels.SelectedObject = tag.ModelEntries[i];
                            olvModels.EnsureModelVisible(tag.ModelEntries[i]);
                            break;
                        }
                    }
                }
            }
            else
            {
                olvModels.ClearObjects();
                olvPrompt.ClearObjects();
                kryptonTBResponse.Clear();
            }
        }

        private void AddRun(RunEntry runEntry, bool select)
        {
            olvRuns.AddObject(runEntry);
            if (select && olvRuns.SelectedIndices.Count == 0)
            {
                olvRuns.SelectedObject = runEntry;
                olvRuns.EnsureModelVisible(runEntry);
            }

            UpdateRibbonEnabledState();
        }

        private bool GetMultiSelected<T>(TypedObjectListView<T> lv, out List<T> selected) where T : class
        {
            selected = (List<T>)lv.SelectedObjects;
            return selected.Count > 0;
        }
        private bool GetSelectedRuns(out List<RunEntry> selected) => GetMultiSelected(tolvRuns, out selected);
        private bool GetSelectedModels(out List<ModelEntry> selected) => GetMultiSelected(tolvModels, out selected);
        private bool GetSelectedPrompts(out List<Message> selected) => GetMultiSelected(tolvPrompt, out selected);

        private bool GetSingleSelected<T>(TypedObjectListView<T> lv, out T selected) where T : class
        {
            selected = lv.SelectedObject;
            return selected != null;
        }
        private bool GetSelectedRun(out RunEntry selected) => GetSingleSelected(tolvRuns, out selected);
        private bool GetSelectedModel(out ModelEntry selected) => GetSingleSelected(tolvModels, out selected);
        private bool GetSelectedPrompt(out Message selected) => GetSingleSelected(tolvPrompt, out selected);

        private bool RemoveSelectedRuns()
        {
            bool hadItems = false;
            if (GetSelectedRuns(out List<RunEntry> items))
            {
                olvRuns.RemoveObjects(items);
                hadItems = true;
            }

            UpdateRibbonEnabledState();
            
            return hadItems;
        }

        private void MovePage(KryptonPage page, KryptonNavigator target)
        {
            if (page?.KryptonParentContainer is KryptonNavigator nav && target != null)
            {
                nav.Pages.Remove(page);
                target.Pages.Add(page);
            }
        }

        private KryptonPage GetPageByName(string name)
        {
            switch (name)
            {
                case "Runs":
                    return kryptonPageRuns;
                case "Models":
                    return kryptonPageModels;
                case "Prompt":
                    return kryptonPagePrompt;
                case "Response":
                    return kryptonPageResponse;
                default:
                    return null;
            }
        }

        private void SetEnabledState(bool enabled)
        {
            kryptonRibbonGroupButtonLoadRuns.Enabled = enabled;
            kryptonRibbonGroupButtonUnloadModel.Enabled = enabled && apiConnected;
            Busy = !enabled;
        }

        private void UpdateRibbonEnabledState()
        {
            bool hasRuns = olvRuns.GetItemCount() > 0;
            if (kryptonRibbonGroupButtonSaveRuns.Enabled != hasRuns)
                kryptonRibbonGroupButtonSaveRuns.Enabled = hasRuns;
            if (kryptonRibbonGroupButtonGenerate.Enabled != hasRuns && !Busy)
                kryptonRibbonGroupButtonGenerate.Enabled = hasRuns;
            if (kryptonRibbonQATButtonGenerate.Enabled != hasRuns && !Busy)
                kryptonRibbonQATButtonGenerate.Enabled = hasRuns;
        }

        private void UpdateStatus()
        {
            if (updatingStatus)
                return;

            updatingStatus = true;

            kryptonRibbonGroupButtonConnect.ImageLarge = imgUnknown;
            kryptonRibbonQATButtonConnect.Image = imgBulletBlack;
            Task.Run(async () =>
            {
                var models = await WebUIAPI.GetModelsInternal(1000);
                var model = await WebUIAPI.GetModelInfoInternal(1000);
                if (model.Success && models.Success)
                {
                    modelNames = models.Data.ModelNames;
                    kryptonRibbon1.Invoke((MethodInvoker)delegate
                    {
                        kryptonRibbonGroupButtonConnect.ImageLarge = imgAccept;
                        kryptonRibbonQATButtonConnect.Image = imgBulletGreen;
                        kryptonRibbonGroupTextBoxLoadedModel.Text = model.Data.ModelName;
                        kryptonRibbonGroupLabelAvailableModels.TextLine1 = $"Available Models: {models.Data.ModelNames.Count}";
                        kryptonRibbonGroupButtonUnloadModel.Enabled = !Busy;
                        apiConnected = true;
                    });
                }
                else
                {
                    modelNames = new List<string>();
                    kryptonRibbon1.Invoke((MethodInvoker)delegate
                    {
                        kryptonRibbonGroupButtonConnect.ImageLarge = imgExclamation;
                        kryptonRibbonQATButtonConnect.Image = imgBulletRed;
                        kryptonRibbonGroupTextBoxLoadedModel.Text = "None";
                        kryptonRibbonGroupLabelAvailableModels.TextLine1 = $"Available Models: 0";
                        kryptonRibbonGroupButtonUnloadModel.Enabled = false;
                        apiConnected = false;
                    });
                }
            });

            updatingStatus = false;
        }

        private void UpdateGenerateButtonState(bool isGenerating)
        {
            if (isGenerating)
            {
                kryptonRibbonGroupButtonGenerate.TextLine1 = "Cancel";
                kryptonRibbonGroupButtonGenerate.ImageLarge = imgStop;
                kryptonRibbonQATButtonGenerate.Image = imgStop16;
            }
            else
            {
                kryptonRibbonGroupButtonGenerate.TextLine1 = "Generate";
                kryptonRibbonGroupButtonGenerate.ImageLarge = imgGenerate;
                kryptonRibbonGroupButtonGenerate.Enabled = true;
                kryptonRibbonQATButtonGenerate.Image = imgGenerate16;
                kryptonRibbonQATButtonGenerate.Enabled = true;
            }
        }

        private async Task Generate(List<RunEntry> runs)
        {
            var genRuns = new List<GenerationRun>();
            for (int i = 0; i < runs.Count; i++)
            {
                //Skips check for HasModels, as Generate(List<GenerationRun>) already does that.
                genRuns.Add(new GenerationRun(runs[i]));
            }
            await Generate(genRuns);
        }

        private async Task Generate(RunEntry run, List<ModelEntry> models)
        {
            await Generate(new GenerationRun(run, models));
        }

        private async Task Generate(GenerationRun run) => await Generate(new List<GenerationRun>() { run });
        private async Task Generate(List<GenerationRun> runs)
        {
            SetEnabledState(false);

            job = new GenerationJob(settings.WebUIServer, settings.WebUIPort, settings.WebUIOAuthToken, settings.Temperature, settings.FrequencyPenalty, settings.PresencePenalty, settings.TopP, settings.TopK, settings.MaxTokens, settings.MaxTokensSecond);

            for (int i = 0; i < runs.Count; i++)
            {
                var genRun = runs[i];
                if (genRun.HasModels)
                {
                    job.Runs.Add(genRun);
                }
            }

            if (job.HasRuns)
            {
                UpdateGenerateButtonState(true);
                tsslStatus.Text = "Initializing Inference";
                tspbProgress.Value = 0;

                job.Schedule();
                job.GenerationLoadingModel += GenerationLoadingModel;
                job.GenerationLoadedModel += GenerationLoadedModel;
                job.GenerationUpdate += GenerationUpdateHandler;
                await job.ProcessJob(settings.InferenceOrder);
                job.GenerationLoadingModel -= GenerationLoadingModel;
                job.GenerationLoadedModel -= GenerationLoadedModel;
                job.GenerationUpdate -= GenerationUpdateHandler;
                job.Unschedule();

                tspbProgress.Value = 100;
                UpdateGenerateButtonState(false);
            }

            bool aborted = job.JobCancellated;
            if (aborted)
                tsslStatus.Text = "Inference Aborted";
            else if (!job.HasRuns)
                tsslStatus.Text = "Nothing to generate";
            else
                tsslStatus.Text = "Inference Completed";

            if (job.LoadModelFailure > 0)
                tsslStatus.Text += $", failed to load {job.LoadModelFailure} models";

            if (job.GetModelInfoFailure > 0)
                tsslStatus.Text += $", API calls failed";

            job = null;

            SetEnabledState(true);
        }

        private void GenerationLoadingModel(object sender, GenerationLoadingModelEventArgs e)
        {
            tsslStatus.Text = $"Loading Model: {e.Model}";
        }

        private void GenerationLoadedModel(object sender, GenerationLoadedModelEventArgs e)
        {
            kryptonRibbonGroupButtonConnect.ImageLarge = imgAccept;
            kryptonRibbonQATButtonConnect.Image = imgBulletGreen;
            kryptonRibbonGroupTextBoxLoadedModel.Text = e.Model;
            apiConnected = true;
        }

        private void GenerationUpdateHandler(object sender, GenerationUpdateEventArgs e)
        {
            if (e.Messages.Count == 0)
                return;

            e.Model.SetResponseMessages(e.Messages);
            if (markingList.Count > 0)
                e.Model.SetMarked(markingList);

            if (tolvModels.SelectedObject == e.Model)
                PopulateResponse(e.Model.ResponseFormatted);

            if (e.State == GenerationUpdateState.First)
            {
                tsslStatus.Text = $"Running Inference for: Run {olvRuns.ModelToItem(e.RunModel).Index + 1} - {e.Model}";
                tspbProgress.Value = MathUtil.Clamp((int)Math.Floor(((float)e.Current / e.Total) * 100f), 0, 100);

                if (buttonSpecAnyModelTrack.Checked == ButtonCheckState.Checked)
                {
                    bool selectedRun = GetSelectedRun(out RunEntry run);
                    if (!selectedRun || (selectedRun && e.RunModel != run))
                    {
                        olvRuns.SelectedObject = e.RunModel;
                        olvRuns.EnsureModelVisible(e.RunModel);
                    }

                    bool selectedModel = GetSelectedModel(out ModelEntry item);
                    if (!selectedModel || (selectedModel && e.Model != item))
                    {
                        olvModels.SelectedObject = e.Model;
                        olvModels.EnsureModelVisible(e.Model);
                    }
                }
            }
        }

        //=================================================================
        //Public Methods
        //=================================================================
        public bool CloseFromTaskTray()
        {
            if (Disposing || IsDisposed || !Visible)
                return true;

            Close();
            return closing;
        }
    }
}
